/*
 * util.hpp
 *
 *  Created on: May 5, 2009
 *      Author: asantos
 */
#include "../math.hpp"

class Texture{
private: GLuint texture;
public:
	Texture(){
		glGenTextures(1, &texture);
	}
	virtual ~Texture(){
		glBindTexture(GL_TEXTURE_2D, 0);
		glDeleteTextures(1, &texture);
	}
	void Bind(){
		glBindTexture(GL_TEXTURE_2D, texture);
	}
	void Load(GLint width, GLint height, GLint numberOfColors, GLenum textureFormat, const GLvoid* pixels){
		Bind();
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

		glTexImage2D(GL_TEXTURE_2D, 0, numberOfColors, width, height, 0,
						  textureFormat, GL_UNSIGNED_BYTE, pixels);
	}
};

class UnableToCreateSurface : public virtual std::exception{
private:
		const char* msg;
public:
	UnableToCreateSurface(const char* msg):msg(msg){

	}
	char const* what()const throw(){
		return msg;
	}
};
class UnhandledSurfaceFormat : public virtual std::exception{
public:
	UnhandledSurfaceFormat(){

	}
	char const* what()const throw(){
		return "UnhandledSurfaceFormat";
	}
};
class Vertex{
private:
	Vector4 position;
	Vector4 normal;
	Vector3 color;
	Vector2 texturecoords;
public:
	Vertex( float x=0.0f, float y=0.0f, float z=0.0f,
			float nx=0.0f, float ny=0.0f, float nz=0.0f)
	{
		position(0) = x;
		position(1) = y;
		position(2) = z;
		position(3) = 1.0f;
		normal(0) = nx;
		normal(1) = ny;
		normal(2) = nz;
		normal(3) = 1.0f;
		color(0) = 1.0f;
		color(1) = 1.0f;
		color(2) = 1.0f;
		texturecoords(0) = 0.0f;
		texturecoords(1) = 0.0f;

	}
	inline Vector4& GetPosition(){
		return position;
	}
	inline const Vector4& GetPosition()const{
		return position;
	}
	inline float GetX()const{
		return position(0);
	}
	inline float GetY()const{
		return position(1);
	}
	inline float GetZ()const{
		return position(2);
	}
	inline Vector4& GetNormal(){
		return normal;
	}
	inline const Vector4& GetNormal()const{
		return normal;
	}
	inline float GetNormalX()const{
		return normal(0);
	}
	inline float GetNormalY()const{
		return normal(1);
	}
	inline float GetNormalZ()const{
		return normal(2);
	}
	inline Vector3& GetColor(){
		return color;
	}
	inline const Vector3& GetColor()const{
		return color;
	}
	inline float GetColorR()const{
		return color(0);
	}
	inline float GetColorG()const{
		return color(1);
	}
	inline float GetColorB()const{
		return color(2);
	}
	inline Vector2& GetTextureCoordinates(){
		return texturecoords;
	}
	inline const Vector2& GetTextureCoordinates()const{
		return texturecoords;
	}
	inline float GetTextureU()const{
		return texturecoords(0);
	}
	inline float GetTextureV()const{
		return texturecoords(1);
	}
};

class Triangle{
private:
	std::list<Vertex> vertices;
public:
	Triangle(){
	}
	const std::list<Vertex>& GetVertices()const{
		return vertices;
	}
	void Clear(){
		vertices.empty();
	}
	void AddVertex(const Vertex& vertex){
		if(vertices.size()<3){
			vertices.push_back(vertex);
		}
	}
};

class Geometry{
private:
	bool visible;
public:
	Geometry(bool visible = true):visible(visible){}
	virtual ~Geometry(){

	}
	virtual void SetVisibility(bool visibility){
		visible = visibility;
	}
	virtual bool IsVisible()const{
		return visible;
	}
};

class TriangleStrip: public Geometry{
public:
	typedef std::list<Vertex> Vertices;
private:
	std::list<Vertex> vertices;
	Texture texture;
public:
	TriangleStrip(bool visible = true, const Texture& texture = Texture()):Geometry(visible), texture(texture){
	}
	const std::list<Vertex>& GetVertices()const{
		return vertices;
	}
	void Clear(){
		vertices.empty();
	}
	void AddVertex(const Vertex& vertex){
		vertices.push_back(vertex);
	}
};

class IRenderable{
private:
	bool visible;
public:
	IRenderable(bool visible=true):visible(visible){}
	virtual ~IRenderable(){

	}
	virtual void SetVisibility(bool visibility){
		visible = visibility;
	}
	virtual bool IsVisible()const{
		return visible;
	}

	virtual void Render()const = 0;

};

class IRenderableFactory{
public:
	virtual const IRenderable& CreateFromTriangleStrip(const TriangleStrip& trianglestrip) = 0;
};

class DisplayList: public IRenderable{
private:
	GLuint displaylist;
public:
	DisplayList(GLuint displaylist, bool visible):IRenderable(visible), displaylist(displaylist){

	}
	virtual void Render()const {
		if(this->IsVisible()){
			glCallList(displaylist);
		}
	}
};

class VBO: public IRenderable{
private:
	GLuint colorBuffer;
	GLuint colorBufferSize;

	GLuint normalBuffer;
	GLuint normalBufferSize;

	GLuint vertexBuffer;
	GLuint vertexBufferSize;

	GLuint textureCoordBuffer;
	GLuint textureCoordBufferSize;

	GLenum usageMode;
public:
	VBO(GLuint colorBufferSize, void* colorData, GLuint normalBufferSize, void* normalData, GLuint vertexBufferSize, void* vertexData, GLuint textureCoordBufferSize, void* textureCoordData, GLenum usageMode){
			glGenBuffers(1, &colorBuffer);
			glGenBuffers(1, &normalBuffer);
			glGenBuffers(1, &vertexBuffer);
			glGenBuffers(1, &textureCoordBuffer);


			this->usageMode = usageMode;

			this->colorBufferSize = colorBufferSize;
			glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
			glBufferData(GL_ARRAY_BUFFER, colorBufferSize, colorData, usageMode);

			this->normalBufferSize = normalBufferSize;
			glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
			glBufferData(GL_ARRAY_BUFFER, normalBufferSize, normalData, usageMode);

			this->vertexBufferSize = vertexBufferSize;
			glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
			glBufferData(GL_ARRAY_BUFFER, vertexBufferSize, vertexData, usageMode);

			this->textureCoordBufferSize = textureCoordBufferSize;
			glBindBuffer(GL_ARRAY_BUFFER, textureCoordBuffer);
			glBufferData(GL_ARRAY_BUFFER, textureCoordBufferSize, textureCoordData, usageMode);
		}
	virtual ~VBO(){
		/*glBindBuffer(GL_ARRAY_BUFFER, 0);
		glDeleteBuffers(1, &colorBuffer);
		glDeleteBuffers(1, &normalBuffer);
		glDeleteBuffers(1, &vertexBuffer);
		glDeleteBuffers(1, &textureCoordBuffer);*/
	}
	void Render()const{
		glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
		glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);

		/*glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
		glNormalPointer(3, GL_FLOAT, 0, 0);*/

		glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
		glVertexPointer(3, GL_FLOAT, 0, 0);

		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBufferSize/3);


		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);

		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}
};


class VBOFactory{
public:
	VBOFactory(){}
	VBO CreateFromTriangleStrip(const TriangleStrip& trianglestrip){
		const std::list<Vertex>& vertices = trianglestrip.GetVertices();
		GLuint vertexBufferSize = vertices.size();
		GLfloat* vertexData = new GLfloat[vertices.size()*3];
		std::list<Vertex>::const_iterator itr = vertices.begin();
		for(unsigned int i=0; i<vertexBufferSize && itr!=vertices.end(); i+=3, itr++){
			vertexData[i]=itr->GetX();
			vertexData[i+1]=itr->GetY();
			vertexData[i+2]=itr->GetZ();
		}
		return VBO(0, 0, 0, 0, vertexBufferSize, vertexData, 0, 0, GL_STATIC_DRAW);
	}
};

class DisplayListFactory: public IRenderableFactory{
private:
	typedef std::list<GLuint> GLDisplayLists;
	typedef std::list<DisplayList*> DisplayLists;
	GLDisplayLists gldisplaylists;
	DisplayLists displaylists;
public:
	DisplayListFactory(){}
	virtual ~DisplayListFactory(){
		{
			GLDisplayLists::const_iterator itr = gldisplaylists.begin();
			for(;itr!=gldisplaylists.end();itr++){
				glDeleteLists(*itr, 1);
			}
		}
		{
			DisplayLists::const_iterator itr = displaylists.begin();
			for(;itr!=displaylists.end();itr++){
				delete *itr;
			}
		}
	}
	IRenderable& CreateFromTriangleStrip(const TriangleStrip& trianglestrip){
		GLuint gldisplaylist = glGenLists(1);

		glNewList(gldisplaylist, GL_COMPILE);
		glBegin(GL_TRIANGLE_STRIP);
		TriangleStrip::Vertices vertices = trianglestrip.GetVertices();
		TriangleStrip::Vertices::const_iterator itr = vertices.begin();
		for(;itr!=vertices.end();itr++){
			glVertex3f(itr->GetX(), itr->GetY(), itr->GetZ());
		}
		glEnd();
		glEndList();
		DisplayList* displaylist = new DisplayList(gldisplaylist, trianglestrip.IsVisible());
		displaylists.push_back(displaylist);
		return *displaylist;
	}
};
